/*
 * Copyright (C) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate::common::constants::*;
use crate::{log_e, singleton_registry, Vec, Box};
use crate::traits::user_db_manager::{Credential, CredentialFilter, User, UserDbManagerRegistry};
use crate::commands::auth_commands::{ReuseUnlockParam, CheckReuseUnlockResultOutput};
use crate::commands::common_command::{EnrolledState};
use crate::traits::time_keeper::TimeKeeperRegistry;
use crate::utils::auth_token::{AuthToken};

const REUSED_UNLOCK_TOKEN_PERIOD: u64 = 5 * 60 * 1000;

pub trait AuthResultReuse {
    fn check_reuse_auth_result(&mut self, param: &ReuseUnlockParam) -> Result<(Vec<u8>, AuthType, EnrolledState), ErrorCode>;
    fn cache_any_auth_result(&mut self, user_id:i32, sec_uid: u64, auth_token: &AuthToken);
    fn cache_unlock_auth_result(&mut self, user_id:i32, sec_uid: u64, auth_token: &AuthToken);
}

#[derive(Clone, Copy)]
struct UnlockAuthResultCache {
    pub is_cached: bool,
    pub user_id: i32,
    pub secure_uid: u64,
    pub auth_token: AuthToken,
}

struct AuthResultReuseDefault {
    unlock_auth_result: UnlockAuthResultCache,
    any_auth_result: UnlockAuthResultCache,
}

impl AuthResultReuseDefault {
fn check_cache_auth_token(param: &ReuseUnlockParam, cache_token: &UnlockAuthResultCache) -> bool {
    if !cache_token.is_cached {
        log_e!("invalid cached unlock token");
        return false;
    }
    if (cache_token.auth_token.schedule_mode != ScheduleMode::Auth &&
        cache_token.auth_token.schedule_mode != ScheduleMode::Enroll) ||
        (cache_token.auth_token.token_type != TokenType::LocalAuth) {
            log_e!("need local auth");
            return false;
    }

    let current_time = match TimeKeeperRegistry::get().get_system_time() {
        Ok(value) => value,
        Err(_) => return false,
    };
    if current_time < cache_token.auth_token.time {
        log_e!("bad time, currentTime:{}, tokenTime:{}", current_time, cache_token.auth_token.time);
        return false;
    }
    if current_time - cache_token.auth_token.time > REUSED_UNLOCK_TOKEN_PERIOD {
        log_e!("cache token timeout, currentTime:{}, tokenTime:{}", current_time, cache_token.auth_token.time);
        return false;
    }
    if current_time - cache_token.auth_token.time > param.reuse_unlock_result_duration {
        log_e!("check reuse duration fail, currentTime:{}, tokenTime:{}, reuseDuration:{}",
            current_time, cache_token.auth_token.time, param.reuse_unlock_result_duration);
        return false;
    }

    if param.base_param.user_id != cache_token.user_id {
        log_e!("mismatch userid:{},{}", param.base_param.user_id, cache_token.user_id);
        return false;
    }
    let user_info = match UserDbManagerRegistry::get().get_user(param.base_param.user_id) {
        Ok(value) => value,
        Err(_) => return false,
    };
    if cache_token.auth_token.secure_uid != user_info.sec_uid {
        log_e!("mismatch secUid:{},{}", cache_token.auth_token.secure_uid, user_info.sec_uid);
        return false;
    }

    if param.base_param.auth_trust_level as i32 > cache_token.auth_token.auth_trust_level as i32 {
        log_e!("check authTrustLevel fail, param atl:{}, token atl:{}", param.base_param.auth_trust_level as i32,
            cache_token.auth_token.auth_trust_level as i32);
        return false;
    }

    if param.reuse_unlock_result_mode == ReuseMode::AuthTypeIrrelevant ||
        param.reuse_unlock_result_mode == ReuseMode::CallerIrrelevantAuthTypeIrrelevant {
            return true;
        }
    if !param.auth_types.contains(&cache_token.auth_token.auth_type) {
        log_e!("check authType fail, tokenAuthType:{}", cache_token.auth_token.auth_type as i32);
        return false;
    }
    return true;
}

fn get_reuse_auth_result(&mut self, param: &ReuseUnlockParam) -> Result<AuthToken, ErrorCode> {
    let cache_token = match param.reuse_unlock_result_mode {
        ReuseMode::AuthTypeRelevant | ReuseMode::AuthTypeIrrelevant => &mut self.unlock_auth_result,
        _ => &mut self.any_auth_result,
    };


    if !Self::check_cache_auth_token(param, cache_token) {
        log_e!("check cache auth token fail");
        cache_token.is_cached = false;
        return Err(ErrorCode::ReuseAuthResultFailed);
    }

    let mut reuse_token = cache_token.auth_token.clone();
    reuse_token.schedule_mode = ScheduleMode::ReuseUnlockAuthResult;
    reuse_token.token_type = TokenType::LocalResign;
    Ok(reuse_token)
}
}

impl AuthResultReuse for AuthResultReuseDefault {
fn cache_any_auth_result(&mut self, user_id:i32, sec_uid: u64, auth_token: &AuthToken) {
    self.any_auth_result.is_cached = true;
    self.any_auth_result.user_id = user_id;
    self.any_auth_result.secure_uid = sec_uid;
    self.any_auth_result.auth_token = *auth_token;
}

fn cache_unlock_auth_result(&mut self, user_id:i32, sec_uid: u64, auth_token: &AuthToken) {
    self.unlock_auth_result.is_cached = true;
    self.unlock_auth_result.user_id = user_id;
    self.unlock_auth_result.secure_uid = sec_uid;
    self.unlock_auth_result.auth_token = *auth_token;
}

fn check_reuse_auth_result(&mut self, param: &ReuseUnlockParam) -> Result<(Vec<u8>, AuthType, EnrolledState), ErrorCode> {
    if param.reuse_unlock_result_duration == 0 || param.reuse_unlock_result_duration > REUSED_UNLOCK_TOKEN_PERIOD {
        log_e!("bad reuse duration:{}", param.reuse_unlock_result_duration);
        return Err(ErrorCode::BadParam);
    }

    let mut reuse_token_temp = self.get_reuse_auth_result(param)?;
    let token_auth_type = reuse_token_temp.auth_type;
    let reuse_token = reuse_token_temp.encode_to_slice()?;

    let user_id = param.base_param.user_id;
    let credential_filter: CredentialFilter = Box::new(move |cred: &Credential| {
        cred.user_id == user_id && cred.auth_type == token_auth_type
    });
    let cred_infos = match UserDbManagerRegistry::get().get_credential_list(credential_filter) {
        Ok(value) => value,
        Err(err) => return Err(err),
    };

    let enroll_state = EnrolledState {
        credential_digest : UserDbManagerRegistry::get().get_enrolled_id(user_id, token_auth_type)?,
        credential_count : cred_infos.len() as u16,
    };
    Ok((reuse_token, token_auth_type, enroll_state))
}
}

struct DummyAuthResultReuse;
impl AuthResultReuse for DummyAuthResultReuse {
    fn check_reuse_auth_result(&mut self, _param: &ReuseUnlockParam) -> Result<(Vec<u8>, AuthType, EnrolledState), ErrorCode> {
        log_e!("not implemented");
        Err(ErrorCode::GeneralError)
    }

    fn cache_any_auth_result(&mut self, _user_id:i32, _sec_uid: u64, _auth_token: &AuthToken) {
        log_e!("not implemented");
    }

    fn cache_unlock_auth_result(&mut self, _user_id:i32, _sec_uid: u64, _auth_token: &AuthToken) {
        log_e!("not implemented");
    }
}
singleton_registry!(AuthResultReuseRegistry, AuthResultReuse, DummyAuthResultReuse);
